/*! Buttons for DataTables 1.1.0
 * ©2015 SpryMedia Ltd - datatables.net/license
 */
layui.define("jquery", function(exports) {
  var jQuery = layui.jquery;
  (function(factory) {
    if(typeof define === 'function' && define.amd) {
      // AMD
      define(['jquery', 'datatables.net'], function($) {
        return factory($, window, document);
      });
    } else if(typeof exports === 'object') {
      // CommonJS
      module.exports = function(root, $) {
        if(!root) {
          root = window;
        }

        if(!$ || !$.fn.dataTable) {
          $ = require('datatables.net')(root, $).$;
        }

        return factory($, root, root.document);
      };
    } else {
      // Browser
      factory(jQuery, window, document);
    }
  }(function($, window, document, undefined) {
    'use strict';
    var DataTable = $.fn.dataTable;

    // Used for namespacing events added to the document by each instance, so they
    // can be removed on destroy
    var _instCounter = 0;

    // Button namespacing counter for namespacing events on individual buttons
    var _buttonCounter = 0;

    var _dtButtons = DataTable.ext.buttons;

    /**
     * [Buttons description]
     * @param {[type]}
     * @param {[type]}
     */
    var Buttons = function(dt, config) {
      // Allow a boolean true for defaults
      if(config === true) {
        config = {};
      }

      // For easy configuration of buttons an array can be given
      if($.isArray(config)) {
        config = {
          buttons: config
        };
      }

      this.c = $.extend(true, {}, Buttons.defaults, config);

      // Don't want a deep copy for the buttons
      if(config.buttons) {
        this.c.buttons = config.buttons;
      }

      this.s = {
        dt: new DataTable.Api(dt),
        buttons: [],
        subButtons: [],
        listenKeys: '',
        namespace: 'dtb' + (_instCounter++)
      };

      this.dom = {
        container: $('<' + this.c.dom.container.tag + '/>')
          .addClass(this.c.dom.container.className)
      };

      this._constructor();
    };

    $.extend(Buttons.prototype, {
      /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       * Public methods
       */

      /**
       * Get the action of a button
       * @param  {int|string} Button index
       * @return {function}
       */
      /**
       * Set the action of a button
       * @param  {int|string} Button index
       * @param  {function} Function to set
       * @return {Buttons} Self for chaining
       */
      action: function(idx, action) {
        var button = this._indexToButton(idx).conf;

        if(action === undefined) {
          return button.action;
        }

        button.action = action;

        return this;
      },

      /**
       * Add an active class to the button to make to look active
       * @param  {int|string} Button index
       * @param  {boolean} [flag=true] Enable / disable flag
       * @return {Buttons} Self for chaining
       */
      active: function(idx, flag) {
        var button = this._indexToButton(idx);
        button.node.toggleClass(
          this.c.dom.button.active,
          flag === undefined ? true : flag
        );

        return this;
      },

      /**
       * Add a new button
       * @param {int|string} Button index for where to insert the button
       * @param {object} Button configuration object, base string name or function
       * @return {Buttons} Self for chaining
       */
      add: function(idx, config) {
        if(typeof idx === 'string' && idx.indexOf('-') !== -1) {
          var idxs = idx.split('-');
          this.c.buttons[idxs[0] * 1].buttons.splice(idxs[1] * 1, 0, config);
        } else {
          this.c.buttons.splice(idx * 1, 0, config);
        }

        this.dom.container.empty();
        this._buildButtons(this.c.buttons);

        return this;
      },

      /**
       * Get the container node for the buttons
       * @return {jQuery} Buttons node
       */
      container: function() {
        return this.dom.container;
      },

      /**
       * Disable a button
       * @param  {int|string} Button index
       * @return {Buttons} Self for chaining
       */
      disable: function(idx) {
        var button = this._indexToButton(idx);
        button.node.addClass(this.c.dom.button.disabled);

        return this;
      },

      /**
       * Destroy the instance, cleaning up event handlers and removing DOM
       * elements
       * @return {Buttons} Self for chaining
       */
      destroy: function() {
        // Key event listener
        $('body').off('keyup.' + this.s.namespace);

        // Individual button destroy (so they can remove their own events if
        // needed
        var buttons = this.s.buttons;
        var subButtons = this.s.subButtons;
        var i, ien, j, jen;

        for(i = 0, ien = buttons.length; i < ien; i++) {
          this.removePrep(i);

          for(j = 0, jen = subButtons[i].length; j < jen; j++) {
            this.removePrep(i + '-' + j);
          }
        }

        this.removeCommit();

        // Container
        this.dom.container.remove();

        // Remove from the settings object collection
        var buttonInsts = this.s.dt.settings()[0];

        for(i = 0, ien = buttonInsts.length; i < ien; i++) {
          if(buttonInsts.inst === this) {
            buttonInsts.splice(i, 1);
            break;
          }
        }

        return this;
      },

      /**
       * Enable / disable a button
       * @param  {int|string} Button index
       * @param  {boolean} [flag=true] Enable / disable flag
       * @return {Buttons} Self for chaining
       */
      enable: function(idx, flag) {
        if(flag === false) {
          return this.disable(idx);
        }

        var button = this._indexToButton(idx);
        button.node.removeClass(this.c.dom.button.disabled);

        return this;
      },

      /**
       * Get the instance name for the button set selector
       * @return {string} Instance name
       */
      name: function() {
        return this.c.name;
      },

      /**
       * Get a button's node
       * @param  {int|string} Button index
       * @return {jQuery} Button element
       */
      node: function(idx) {
        var button = this._indexToButton(idx);
        return button.node;
      },

      /**
       * Tidy up any buttons that have been scheduled for removal. This is
       * required so multiple buttons can be removed without upsetting the button
       * indexes while removing them.
       * @param  {int|string} Button index
       * @return {Buttons} Self for chaining
       */
      removeCommit: function() {
        var buttons = this.s.buttons;
        var subButtons = this.s.subButtons;
        var i, ien, j;

        for(i = buttons.length - 1; i >= 0; i--) {
          if(buttons[i] === null) {
            buttons.splice(i, 1);
            subButtons.splice(i, 1);
            this.c.buttons.splice(i, 1);
          }
        }

        for(i = 0, ien = subButtons.length; i < ien; i++) {
          for(j = subButtons[i].length - 1; j >= 0; j--) {
            if(subButtons[i][j] === null) {
              subButtons[i].splice(j, 1);
              this.c.buttons[i].buttons.splice(j, 1);
            }
          }
        }

        return this;
      },

      /**
       * Scheduled a button for removal. This is required so multiple buttons can
       * be removed without upsetting the button indexes while removing them.
       * @return {Buttons} Self for chaining
       */
      removePrep: function(idx) {
        var button;
        var dt = this.s.dt;

        if(typeof idx === 'number' || idx.indexOf('-') === -1) {
          // Top level button
          button = this.s.buttons[idx * 1];

          if(button.conf.destroy) {
            button.conf.destroy.call(dt.button(idx), dt, button, button.conf);
          }

          button.node.remove();
          this._removeKey(button.conf);
          this.s.buttons[idx * 1] = null;
        } else {
          // Collection button
          var idxs = idx.split('-');
          button = this.s.subButtons[idxs[0] * 1][idxs[1] * 1];

          if(button.conf.destroy) {
            button.conf.destroy.call(dt.button(idx), dt, button, button.conf);
          }

          button.node.remove();
          this._removeKey(button.conf);
          this.s.subButtons[idxs[0] * 1][idxs[1] * 1] = null;
        }

        return this;
      },

      /**
       * Get the text for a button
       * @param  {int|string} Button index
       * @return {string} Button text
       */
      /**
       * Set the text for a button
       * @param  {int|string|function} Button index
       * @param  {string} Text
       * @return {Buttons} Self for chaining
       */
      text: function(idx, label) {
        var button = this._indexToButton(idx);
        var buttonLiner = this.c.dom.collection.buttonLiner;
        var linerTag = typeof idx === 'string' && idx.indexOf('-') !== -1 && buttonLiner && buttonLiner.tag ?
          buttonLiner.tag :
          this.c.dom.buttonLiner.tag;
        var dt = this.s.dt;
        var text = function(opt) {
          return typeof opt === 'function' ?
            opt(dt, button.node, button.conf) :
            opt;
        };

        if(label === undefined) {
          return text(button.conf.text);
        }

        button.conf.text = label;

        if(linerTag) {
          button.node.children(linerTag).html(text(label));
        } else {
          button.node.html(text(label));
        }

        return this;
      },

      /**
       * Calculate button index from a node
       * @param  {node} Button node (_not_ a jQuery object)
       * @return {string} Index. Undefined if not found
       */
      toIndex: function(node) {
        var i, ien, j, jen;
        var buttons = this.s.buttons;
        var subButtons = this.s.subButtons;

        // Loop the main buttons first
        for(i = 0, ien = buttons.length; i < ien; i++) {
          if(buttons[i].node[0] === node) {
            return i + '';
          }
        }

        // Then the sub-buttons
        for(i = 0, ien = subButtons.length; i < ien; i++) {
          for(j = 0, jen = subButtons[i].length; j < jen; j++) {
            if(subButtons[i][j].node[0] === node) {
              return i + '-' + j;
            }
          }
        }
      },

      /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       * Constructor
       */

      /**
       * Buttons constructor
       * @private
       */
      _constructor: function() {
        var that = this;
        var dt = this.s.dt;
        var dtSettings = dt.settings()[0];

        if(!dtSettings._buttons) {
          dtSettings._buttons = [];
        }

        dtSettings._buttons.push({
          inst: this,
          name: this.c.name
        });

        this._buildButtons(this.c.buttons);

        dt.on('destroy', function() {
          that.destroy();
        });

        // Global key event binding to listen for button keys
        $('body').on('keyup.' + this.s.namespace, function(e) {
          if(!document.activeElement || document.activeElement === document.body) {
            // SUse a string of characters for fast lookup of if we need to
            // handle this
            var character = String.fromCharCode(e.keyCode).toLowerCase();

            if(that.s.listenKeys.toLowerCase().indexOf(character) !== -1) {
              that._keypress(character, e);
            }
          }
        });
      },

      /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       * Private methods
       */

      /**
       * Add a new button to the key press listener
       * @param {object} Resolved button configuration object
       * @private
       */
      _addKey: function(conf) {
        if(conf.key) {
          this.s.listenKeys += $.isPlainObject(conf.key) ?
            conf.key.key :
            conf.key;
        }
      },

      /**
       * Create buttons from an array of buttons
       * @param  {array} Buttons to create
       * @param  {jQuery} Container node into which the created button should be
       *   inserted.
       * @param  {int} Counter for sub-buttons to be stored in a collection
       * @private
       */
      _buildButtons: function(buttons, container, collectionCounter) {
        var dt = this.s.dt;

        if(!container) {
          container = this.dom.container;
          this.s.buttons = [];
          this.s.subButtons = [];
        }

        for(var i = 0, ien = buttons.length; i < ien; i++) {
          var conf = this._resolveExtends(buttons[i]);

          if(!conf) {
            continue;
          }

          // If the configuration is an array, then expand the buttons at this
          // point
          if($.isArray(conf)) {
            this._buildButtons(conf, container, collectionCounter);
            continue;
          }

          var button = this._buildButton(
            conf,
            collectionCounter !== undefined ? true : false
          );

          if(!button) {
            continue;
          }

          var buttonNode = button.node;
          container.append(button.inserter);

          if(collectionCounter === undefined) {
            this.s.buttons.push({
              node: buttonNode,
              conf: conf,
              inserter: button.inserter
            });
            this.s.subButtons.push([]);
          } else {
            this.s.subButtons[collectionCounter].push({
              node: buttonNode,
              conf: conf,
              inserter: button.inserter
            });
          }

          if(conf.buttons) {
            var collectionDom = this.c.dom.collection;
            conf._collection = $('<' + collectionDom.tag + '/>')
              .addClass(collectionDom.className);

            this._buildButtons(conf.buttons, conf._collection, i);
          }

          // init call is made here, rather than buildButton as it needs to
          // have been added to the buttons / subButtons array first
          if(conf.init) {
            conf.init.call(dt.button(buttonNode), dt, buttonNode, conf);
          }
        }
      },

      /**
       * Create an individual button
       * @param  {object} config            Resolved button configuration
       * @param  {boolean} collectionButton `true` if a collection button
       * @return {jQuery} Created button node (jQuery)
       * @private
       */
      _buildButton: function(config, collectionButton) {
        var that = this;
        var buttonDom = this.c.dom.button;
        var linerDom = this.c.dom.buttonLiner;
        var collectionDom = this.c.dom.collection;
        var dt = this.s.dt;
        var text = function(opt) {
          return typeof opt === 'function' ?
            opt(dt, button, config) :
            opt;
        };

        if(collectionButton && collectionDom.button) {
          buttonDom = collectionDom.button;
        }

        if(collectionButton && collectionDom.buttonLiner) {
          linerDom = collectionDom.buttonLiner;
        }

        // Make sure that the button is available based on whatever requirements
        // it has. For example, Flash buttons require Flash
        if(config.available && !config.available(dt, config)) {
          return false;
        }

        var button = $('<' + buttonDom.tag + '/>')
          .addClass(buttonDom.className)
          .attr('tabindex', this.s.dt.settings()[0].iTabIndex)
          .attr('aria-controls', this.s.dt.table().node().id)
          .on('click.dtb', function(e) {
            e.preventDefault();

            if(!button.hasClass(buttonDom.disabled) && config.action) {
              config.action.call(dt.button(button), e, dt, button, config);
            }

            button.blur();
          })
          .on('keyup.dtb', function(e) {
            if(e.keyCode === 13) {
              if(!button.hasClass(buttonDom.disabled) && config.action) {
                config.action.call(dt.button(button), e, dt, button, config);
              }
            }
          });

        if(linerDom.tag) {
          button.append(
            $('<' + linerDom.tag + '/>')
            .html(text(config.text))
            .addClass(linerDom.className)
          );
        } else {
          button.html(text(config.text));
        }

        if(config.enabled === false) {
          button.addClass(buttonDom.disabled);
        }

        if(config.className) {
          button.addClass(config.className);
        }

        if(config.titleAttr) {
          button.attr('title', config.titleAttr);
        }

        if(!config.namespace) {
          config.namespace = '.dt-button-' + (_buttonCounter++);
        }

        var buttonContainer = this.c.dom.buttonContainer;
        var inserter;
        if(buttonContainer) {
          inserter = $('<' + buttonContainer.tag + '/>')
            .addClass(buttonContainer.className)
            .append(button);
        } else {
          inserter = button;
        }

        this._addKey(config);

        return {
          node: button,
          inserter: inserter
        };
      },

      /**
       * Get a button's host information from a button index
       * @param  {int|string} Button index
       * @return {object} Button information - object contains `node` and `conf`
       *   properties
       * @private
       */
      _indexToButton: function(idx) {
        if(typeof idx === 'number' || idx.indexOf('-') === -1) {
          return this.s.buttons[idx * 1];
        }

        var idxs = idx.split('-');
        return this.s.subButtons[idxs[0] * 1][idxs[1] * 1];
      },

      /**
       * Handle a key press - determine if any button's key configured matches
       * what was typed and trigger the action if so.
       * @param  {string} The character pressed
       * @param  {object} Key event that triggered this call
       * @private
       */
      _keypress: function(character, e) {
        var i, ien, j, jen;
        var buttons = this.s.buttons;
        var subButtons = this.s.subButtons;
        var run = function(conf, node) {
          if(!conf.key) {
            return;
          }

          if(conf.key === character) {
            node.click();
          } else if($.isPlainObject(conf.key)) {
            if(conf.key.key !== character) {
              return;
            }

            if(conf.key.shiftKey && !e.shiftKey) {
              return;
            }

            if(conf.key.altKey && !e.altKey) {
              return;
            }

            if(conf.key.ctrlKey && !e.ctrlKey) {
              return;
            }

            if(conf.key.metaKey && !e.metaKey) {
              return;
            }

            // Made it this far - it is good
            node.click();
          }
        };

        // Loop the main buttons first
        for(i = 0, ien = buttons.length; i < ien; i++) {
          run(buttons[i].conf, buttons[i].node);
        }

        // Then the sub-buttons
        for(i = 0, ien = subButtons.length; i < ien; i++) {
          for(j = 0, jen = subButtons[i].length; j < jen; j++) {
            run(subButtons[i][j].conf, subButtons[i][j].node);
          }
        }
      },

      /**
       * Remove a key from the key listener for this instance (to be used when a
       * button is removed)
       * @param  {object} Button configuration
       */
      _removeKey: function(conf) {
        if(conf.key) {
          var character = $.isPlainObject(conf.key) ?
            conf.key.key :
            conf.key;

          // Remove only one character, as multiple buttons could have the
          // same listening key
          var a = this.s.listenKeys.split('');
          var idx = $.inArray(character, a);
          a.splice(idx, 1);
          this.s.listenKeys = a.join('');
        }
      },

      /**
       * Resolve a button configuration
       * @param  {string|function|object} Button config to resolve
       * @return {object} Button configuration
       */
      _resolveExtends: function(conf) {
        var dt = this.s.dt;
        var i, ien;
        var toConfObject = function(base) {
          var loop = 0;

          // Loop until we have resolved to a button configuration, or an
          // array of button configurations (which will be iterated
          // separately)
          while(!$.isPlainObject(base) && !$.isArray(base)) {
            if(base === undefined) {
              return;
            }

            if(typeof base === 'function') {
              base = base(dt, conf);

              if(!base) {
                return false;
              }
            } else if(typeof base === 'string') {
              if(!_dtButtons[base]) {
                throw 'Unknown button type: ' + base;
              }

              base = _dtButtons[base];
            }

            loop++;
            if(loop > 30) {
              // Protect against misconfiguration killing the browser
              throw 'Buttons: Too many iterations';
            }
          }

          return $.isArray(base) ?
            base :
            $.extend({}, base);
        };

        conf = toConfObject(conf);

        while(conf && conf.extend) {
          // Use `toConfObject` in case the button definition being extended
          // is itself a string or a function
          if(!_dtButtons[conf.extend]) {
            throw 'Cannot extend unknown button type: ' + conf.extend;
          }

          var objArray = toConfObject(_dtButtons[conf.extend]);
          if($.isArray(objArray)) {
            return objArray;
          } else if(!objArray) {
            // This is a little brutal as it might be possible to have a
            // valid button without the extend, but if there is no extend
            // then the host button would be acting in an undefined state
            return false;
          }

          // Stash the current class name
          var originalClassName = objArray.className;

          conf = $.extend({}, objArray, conf);

          // The extend will have overwritten the original class name if the
          // `conf` object also assigned a class, but we want to concatenate
          // them so they are list that is combined from all extended buttons
          if(originalClassName && conf.className !== originalClassName) {
            conf.className = originalClassName + ' ' + conf.className;
          }

          // Buttons to be added to a collection  -gives the ability to define
          // if buttons should be added to the start or end of a collection
          var postfixButtons = conf.postfixButtons;
          if(postfixButtons) {
            if(!conf.buttons) {
              conf.buttons = [];
            }

            for(i = 0, ien = postfixButtons.length; i < ien; i++) {
              conf.buttons.push(postfixButtons[i]);
            }

            conf.postfixButtons = null;
          }

          var prefixButtons = conf.prefixButtons;
          if(prefixButtons) {
            if(!conf.buttons) {
              conf.buttons = [];
            }

            for(i = 0, ien = prefixButtons.length; i < ien; i++) {
              conf.buttons.splice(i, 0, prefixButtons[i]);
            }

            conf.prefixButtons = null;
          }

          // Although we want the `conf` object to overwrite almost all of
          // the properties of the object being extended, the `extend`
          // property should come from the object being extended
          conf.extend = objArray.extend;
        }

        return conf;
      }
    });

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * Statics
     */

    /**
     * Show / hide a background layer behind a collection
     * @param  {boolean} Flag to indicate if the background should be shown or
     *   hidden 
     * @param  {string} Class to assign to the background
     * @static
     */
    Buttons.background = function(show, className, fade) {
      if(fade === undefined) {
        fade = 400;
      }

      if(show) {
        $('<div/>')
          .addClass(className)
          .css('display', 'none')
          .appendTo('body')
          .fadeIn(fade);
      } else {
        $('body > div.' + className)
          .fadeOut(fade, function() {
            $(this).remove();
          });
      }
    };

    /**
     * Instance selector - select Buttons instances based on an instance selector
     * value from the buttons assigned to a DataTable. This is only useful if
     * multiple instances are attached to a DataTable.
     * @param  {string|int|array} Instance selector - see `instance-selector`
     *   documentation on the DataTables site
     * @param  {array} Button instance array that was attached to the DataTables
     *   settings object
     * @return {array} Buttons instances
     * @static
     */
    Buttons.instanceSelector = function(group, buttons) {
      if(!group) {
        return $.map(buttons, function(v) {
          return v.inst;
        });
      }

      var ret = [];
      var names = $.map(buttons, function(v) {
        return v.name;
      });

      // Flatten the group selector into an array of single options
      var process = function(input) {
        if($.isArray(input)) {
          for(var i = 0, ien = input.length; i < ien; i++) {
            process(input[i]);
          }
          return;
        }

        if(typeof input === 'string') {
          if(input.indexOf(',') !== -1) {
            // String selector, list of names
            process(input.split(','));
          } else {
            // String selector individual name
            var idx = $.inArray($.trim(input), names);

            if(idx !== -1) {
              ret.push(buttons[idx].inst);
            }
          }
        } else if(typeof input === 'number') {
          // Index selector
          ret.push(buttons[input].inst);
        }
      };

      process(group);

      return ret;
    };

    /**
     * Button selector - select one or more buttons from a selector input so some
     * operation can be performed on them.
     * @param  {array} Button instances array that the selector should operate on
     * @param  {string|int|node|jQuery|array} Button selector - see
     *   `button-selector` documentation on the DataTables site
     * @return {array} Array of objects containing `inst` and `idx` properties of
     *   the selected buttons so you know which instance each button belongs to.
     * @static
     */
    Buttons.buttonSelector = function(insts, selector) {
      var ret = [];
      var run = function(selector, inst) {
        var i, ien, j, jen;
        var buttons = [];

        $.each(inst.s.buttons, function(i, v) {
          if(v !== null) {
            buttons.push({
              node: v.node[0],
              name: v.name
            });
          }
        });

        $.each(inst.s.subButtons, function(i, v) {
          $.each(v, function(j, w) {
            if(w !== null) {
              buttons.push({
                node: w.node[0],
                name: w.name
              });
            }
          });
        });

        var nodes = $.map(buttons, function(v) {
          return v.node;
        });

        if($.isArray(selector) || selector instanceof $) {
          for(i = 0, ien = selector.length; i < ien; i++) {
            run(selector[i], inst);
          }
          return;
        }

        if(selector === null || selector === undefined || selector === '*') {
          // Select all
          for(i = 0, ien = buttons.length; i < ien; i++) {
            ret.push({
              inst: inst,
              idx: inst.toIndex(buttons[i].node)
            });
          }
        } else if(typeof selector === 'number') {
          // Main button index selector
          ret.push({
            inst: inst,
            idx: selector
          });
        } else if(typeof selector === 'string') {
          if(selector.indexOf(',') !== -1) {
            // Split
            var a = selector.split(',');

            for(i = 0, ien = a.length; i < ien; i++) {
              run($.trim(a[i]), inst);
            }
          } else if(selector.match(/^\d+(\-\d+)?$/)) {
            // Sub-button index selector
            ret.push({
              inst: inst,
              idx: selector
            });
          } else if(selector.indexOf(':name') !== -1) {
            // Button name selector
            var name = selector.replace(':name', '');

            for(i = 0, ien = buttons.length; i < ien; i++) {
              if(buttons[i].name === name) {
                ret.push({
                  inst: inst,
                  idx: inst.toIndex(buttons[i].node)
                });
              }
            }
          } else {
            // jQuery selector on the nodes
            $(nodes).filter(selector).each(function() {
              ret.push({
                inst: inst,
                idx: inst.toIndex(this)
              });
            });
          }
        } else if(typeof selector === 'object' && selector.nodeName) {
          // Node selector
          var idx = $.inArray(selector, nodes);

          if(idx !== -1) {
            ret.push({
              inst: inst,
              idx: inst.toIndex(nodes[idx])
            });
          }
        }
      };

      for(var i = 0, ien = insts.length; i < ien; i++) {
        var inst = insts[i];

        run(selector, inst);
      }

      return ret;
    };

    /**
     * Buttons defaults. For full documentation, please refer to the docs/option
     * directory or the DataTables site.
     * @type {Object}
     * @static
     */
    Buttons.defaults = {
      buttons: ['copy', 'excel', 'csv', 'pdf', 'print'],
      name: 'main',
      tabIndex: 0,
      dom: {
        container: {
          tag: 'div',
          className: 'dt-buttons'
        },
        collection: {
          tag: 'div',
          className: 'dt-button-collection'
        },
        button: {
          tag: 'a',
          className: 'dt-button',
          active: 'active',
          disabled: 'disabled'
        },
        buttonLiner: {
          tag: 'span',
          className: ''
        }
      }
    };

    /**
     * Version information
     * @type {string}
     * @static
     */
    Buttons.version = '1.1.0';

    $.extend(_dtButtons, {
      collection: {
        text: function(dt, button, config) {
          return dt.i18n('buttons.collection', 'Collection');
        },
        className: 'buttons-collection',
        action: function(e, dt, button, config) {
          var background;
          var host = button;
          var hostOffset = host.offset();
          var tableContainer = $(dt.table().container());
          var multiLevel = false;

          // Remove any old collection
          if($('div.dt-button-background').length) {
            multiLevel = $('div.dt-button-collection').offset();
            $(document).trigger('click.dtb-collection');
          }

          config._collection
            .addClass(config.collectionLayout)
            .css('display', 'none')
            .appendTo('body')
            .fadeIn(config.fade);

          var position = config._collection.css('position');

          if(multiLevel && position === 'absolute') {
            config._collection.css({
              top: multiLevel.top + 5, // magic numbers for a little offset
              left: multiLevel.left + 5
            });
          } else if(position === 'absolute') {
            config._collection.css({
              top: hostOffset.top + host.outerHeight(),
              left: hostOffset.left
            });

            var listRight = hostOffset.left + config._collection.outerWidth();
            var tableRight = tableContainer.offset().left + tableContainer.width();
            if(listRight > tableRight) {
              config._collection.css('left', hostOffset.left - (listRight - tableRight));
            }
          } else {
            // Fix position - centre on screen
            var top = config._collection.height() / 2;
            if(top > $(window).height() / 2) {
              top = $(window).height() / 2;
            }

            config._collection.css('marginTop', top * -1);
          }

          if(config.background) {
            Buttons.background(true, config.backgroundClassName, config.fade);
          }

          // Need to break the 'thread' for the collection button being
          // activated by a click - it would also trigger this event
          setTimeout(function() {
            // This is bonkers, but if we don't have a click listener on the
            // background element, iOS Safari will ignore the body click
            // listener below. An empty function here is all that is
            // required to make it work...
            $('div.dt-button-background').on('click.dtb-collection', function() {});

            $('body').on('click.dtb-collection', function(e) {
              if(!$(e.target).parents().andSelf().filter(config._collection).length) {
                config._collection
                  .fadeOut(config.fade, function() {
                    config._collection.detach();
                  });

                $('div.dt-button-background').off('click.dtb-collection');
                Buttons.background(false, config.backgroundClassName, config.fade);

                $('body').off('click.dtb-collection');
              }
            });
          }, 10);
        },
        background: true,
        collectionLayout: '',
        backgroundClassName: 'dt-button-background',
        fade: 400
      },
      copy: function(dt, conf) {
        if(_dtButtons.copyHtml5) {
          return 'copyHtml5';
        }
        if(_dtButtons.copyFlash && _dtButtons.copyFlash.available(dt, conf)) {
          return 'copyFlash';
        }
      },
      csv: function(dt, conf) {
        // Common option that will use the HTML5 or Flash export buttons
        if(_dtButtons.csvHtml5 && _dtButtons.csvHtml5.available(dt, conf)) {
          return 'csvHtml5';
        }
        if(_dtButtons.csvFlash && _dtButtons.csvFlash.available(dt, conf)) {
          return 'csvFlash';
        }
      },
      excel: function(dt, conf) {
        // Common option that will use the HTML5 or Flash export buttons
        if(_dtButtons.excelHtml5 && _dtButtons.excelHtml5.available(dt, conf)) {
          return 'excelHtml5';
        }
        if(_dtButtons.excelFlash && _dtButtons.excelFlash.available(dt, conf)) {
          return 'excelFlash';
        }
      },
      pdf: function(dt, conf) {
        // Common option that will use the HTML5 or Flash export buttons
        if(_dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available(dt, conf)) {
          return 'pdfHtml5';
        }
        if(_dtButtons.pdfFlash && _dtButtons.pdfFlash.available(dt, conf)) {
          return 'pdfFlash';
        }
      },
      pageLength: function(dt, conf) {
        var lengthMenu = dt.settings()[0].aLengthMenu;
        var vals = $.isArray(lengthMenu[0]) ? lengthMenu[0] : lengthMenu;
        var lang = $.isArray(lengthMenu[0]) ? lengthMenu[1] : lengthMenu;
        var text = function(dt) {
          return dt.i18n('buttons.pageLength', {
            "-1": 'Show all rows',
            _: 'Show %d rows'
          }, dt.page.len());
        };

        return {
          extend: 'collection',
          text: text,
          className: 'buttons-page-length',
          buttons: $.map(vals, function(val, i) {
            return {
              text: lang[i],
              action: function(e, dt, button, conf) {
                dt.page.len(val).draw();
              },
              init: function(dt, node, conf) {
                var that = this;
                var fn = function() {
                  that.active(dt.page.len() === val);
                };

                dt.on('length.dt' + conf.namespace, fn);
                fn();
              },
              destroy: function(dt, node, conf) {
                dt.off('length.dt' + conf.namespace);
              }
            };
          }),
          init: function(dt, node, conf) {
            var that = this;
            dt.on('length.dt' + conf.namespace, function() {
              that.text(text(dt));
            });
          },
          destroy: function(dt, node, conf) {
            dt.off('length.dt' + conf.namespace);
          }
        };
      }
    });

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * DataTables API
     *
     * For complete documentation, please refer to the docs/api directory or the
     * DataTables site
     */

    // Buttons group and individual button selector
    DataTable.Api.register('buttons()', function(group, selector) {
      // Argument shifting
      if(selector === undefined) {
        selector = group;
        group = undefined;
      }

      return this.iterator(true, 'table', function(ctx) {
        if(ctx._buttons) {
          return Buttons.buttonSelector(
            Buttons.instanceSelector(group, ctx._buttons),
            selector
          );
        }
      }, true);
    });

    // Individual button selector
    DataTable.Api.register('button()', function(group, selector) {
      // just run buttons() and truncate
      var buttons = this.buttons(group, selector);

      if(buttons.length > 1) {
        buttons.splice(1, buttons.length);
      }

      return buttons;
    });

    // Active buttons
    DataTable.Api.register(['buttons().active()', 'button().active()'], function(flag) {
      return this.each(function(set) {
        set.inst.active(set.idx, flag);
      });
    });

    // Get / set button action
    DataTable.Api.registerPlural('buttons().action()', 'button().action()', function(action) {
      if(action === undefined) {
        return this.map(function(set) {
          return set.inst.action(set.idx);
        });
      }

      return this.each(function(set) {
        set.inst.action(set.idx, action);
      });
    });

    // Enable / disable buttons
    DataTable.Api.register(['buttons().enable()', 'button().enable()'], function(flag) {
      return this.each(function(set) {
        set.inst.enable(set.idx, flag);
      });
    });

    // Disable buttons
    DataTable.Api.register(['buttons().disable()', 'button().disable()'], function() {
      return this.each(function(set) {
        set.inst.disable(set.idx);
      });
    });

    // Get button nodes
    DataTable.Api.registerPlural('buttons().nodes()', 'button().node()', function() {
      var jq = $();

      // jQuery will automatically reduce duplicates to a single entry
      $(this.each(function(set) {
        jq = jq.add(set.inst.node(set.idx));
      }));

      return jq;
    });

    // Get / set button text (i.e. the button labels)
    DataTable.Api.registerPlural('buttons().text()', 'button().text()', function(label) {
      if(label === undefined) {
        return this.map(function(set) {
          return set.inst.text(set.idx);
        });
      }

      return this.each(function(set) {
        set.inst.text(set.idx, label);
      });
    });

    // Trigger a button's action
    DataTable.Api.registerPlural('buttons().trigger()', 'button().trigger()', function() {
      return this.each(function(set) {
        set.inst.node(set.idx).trigger('click');
      });
    });

    // Get the container elements for the button sets selected
    DataTable.Api.registerPlural('buttons().containers()', 'buttons().container()', function() {
      var jq = $();

      // jQuery will automatically reduce duplicates to a single entry
      $(this.each(function(set) {
        jq = jq.add(set.inst.container());
      }));

      return jq;
    });

    // Add a new button
    DataTable.Api.register('button().add()', function(idx, conf) {
      if(this.length === 1) {
        this[0].inst.add(idx, conf);
      }

      return this.button(idx);
    });

    // Destroy the button sets selected
    DataTable.Api.register('buttons().destroy()', function(idx) {
      this.pluck('inst').unique().each(function(inst) {
        inst.destroy();
      });

      return this;
    });

    // Remove a button
    DataTable.Api.registerPlural('buttons().remove()', 'buttons().remove()', function() {
      // Need to split into prep and commit so the indexes remain constant during the remove
      this.each(function(set) {
        set.inst.removePrep(set.idx);
      });

      this.pluck('inst').unique().each(function(inst) {
        inst.removeCommit();
      });

      return this;
    });

    // Information box that can be used by buttons
    var _infoTimer;
    DataTable.Api.register('buttons.info()', function(title, message, time) {
      var that = this;

      if(title === false) {
        $('#datatables_buttons_info').fadeOut(function() {
          $(this).remove();
        });
        clearTimeout(_infoTimer);
        _infoTimer = null;

        return this;
      }

      if(_infoTimer) {
        clearTimeout(_infoTimer);
      }

      if($('#datatables_buttons_info').length) {
        $('#datatables_buttons_info').remove();
      }

      title = title ? '<h2>' + title + '</h2>' : '';

      $('<div id="datatables_buttons_info" class="dt-button-info"/>')
        .html(title)
        .append($('<div/>')[typeof message === 'string' ? 'html' : 'append'](message))
        .css('display', 'none')
        .appendTo('body')
        .fadeIn();

      if(time !== undefined && time !== 0) {
        _infoTimer = setTimeout(function() {
          that.buttons.info(false);
        }, time);
      }

      return this;
    });

    // Get data from the table for export - this is common to a number of plug-in
    // buttons so it is included in the Buttons core library
    DataTable.Api.register('buttons.exportData()', function(options) {
      if(this.context.length) {
        return _exportData(new DataTable.Api(this.context[0]), options);
      }
    });

    var _exportTextarea = $('<textarea/>')[0];
    var _exportData = function(dt, inOpts) {
      var config = $.extend(true, {}, {
        rows: null,
        columns: '',
        modifier: {
          search: 'applied',
          order: 'applied'
        },
        orthogonal: 'display',
        stripHtml: true,
        stripNewlines: true,
        decodeEntities: true,
        trim: true,
        format: {
          header: function(d) {
            return strip(d);
          },
          footer: function(d) {
            return strip(d);
          },
          body: function(d) {
            return strip(d);
          }
        }
      }, inOpts);

      var strip = function(str) {
        if(typeof str !== 'string') {
          return str;
        }

        if(config.stripHtml) {
          str = str.replace(/<.*?>/g, '');
        }

        if(config.trim) {
          str = str.replace(/^\s+|\s+$/g, '');
        }

        if(config.stripNewlines) {
          str = str.replace(/\n/g, ' ');
        }

        if(config.decodeEntities) {
          _exportTextarea.innerHTML = str;
          str = _exportTextarea.value;
        }

        return str;
      };

      var header = dt.columns(config.columns).indexes().map(function(idx, i) {
        return config.format.header(dt.column(idx).header().innerHTML, idx);
      }).toArray();

      var footer = dt.table().footer() ?
        dt.columns(config.columns).indexes().map(function(idx, i) {
          var el = dt.column(idx).footer();
          return config.format.footer(el ? el.innerHTML : '', idx);
        }).toArray() :
        null;

      var rowIndexes = dt.rows(config.rows, config.modifier).indexes().toArray();
      var cells = dt
        .cells(rowIndexes, config.columns)
        .render(config.orthogonal)
        .toArray();
      var columns = header.length;
      var rows = columns > 0 ? cells.length / columns : 0;
      var body = new Array(rows);
      var cellCounter = 0;

      for(var i = 0, ien = rows; i < ien; i++) {
        var row = new Array(columns);

        for(var j = 0; j < columns; j++) {
          row[j] = config.format.body(cells[cellCounter], j, i);
          cellCounter++;
        }

        body[i] = row;
      }

      return {
        header: header,
        footer: footer,
        body: body
      };
    };

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * DataTables interface
     */

    // Attach to DataTables objects for global access
    $.fn.dataTable.Buttons = Buttons;
    $.fn.DataTable.Buttons = Buttons;

    // DataTables creation - check if the buttons have been defined for this table,
    // they will have been if the `B` option was used in `dom`, otherwise we should
    // create the buttons instance here so they can be inserted into the document
    // using the API. Listen for `init` for compatibility with pre 1.10.10, but to
    // be removed in future.
    $(document).on('init.dt plugin-init.dt', function(e, settings, json) {
      if(e.namespace !== 'dt') {
        return;
      }

      var opts = settings.oInit.buttons || DataTable.defaults.buttons;

      if(opts && !settings._buttons) {
        new Buttons(settings, opts).container();
      }
    });

    // DataTables `dom` feature option
    DataTable.ext.feature.push({
      fnInit: function(settings) {
        var api = new DataTable.Api(settings);
        var opts = api.init().buttons || DataTable.defaults.buttons;

        return new Buttons(api, opts).container();
      },
      cFeature: "B"
    });

    return Buttons;
  }));
  exports('datatableButton');
});